{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "52202dda",
   "metadata": {},
   "source": [
    "# Molecular Similarity Search\n",
    "In this example we are performing a similarity retrieval of molecular formulae by generating molecular fingerprints using RDKit. With this fingerprints we use Milvus to perform a search for the most similar result to the target molecular formula."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f634caa",
   "metadata": {},
   "source": [
    "## Data\n",
    "We have already prepared a SMILE file containing 10,000 molecular formulae [test_1w.smi](./smiles-data) from [pubchem](ftp://ftp.ncbi.nlm.nih.gov/pubchem/Compound/CURRENT-Full/SDF). The file is composed of two columns, SMILES and ID. Below is an example of the data: \n",
    "\n",
    "| SMILES | ID |\n",
    "| --------------- | -------------- |\n",
    "| [O-]C(=O)CC(C[N+](C)(C)C)OC(=O)C        | 1 |\n",
    "| OC(=O)CC(C[N+](C)(C)C)OC(=O)C           | 2 |\n",
    "| OC1C=CC=C(C1O)C(=O)O                    | 3 |\n",
    "| CC(CN)O                                 | 4 |\n",
    "| NCC(=O)COP(=O)(O)O                      | 5 |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4b61385",
   "metadata": {},
   "source": [
    "## Requirements\n",
    "\n",
    "Before running this notebook, you need to install [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) or [miniconda](https://docs.conda.io/en/latest/miniconda.html) locally. This is required due to [RDKit](https://www.rdkit.org/docs/Install.html) only being installable through conda. Once conda is setup, you need to active a conda enviroment running `Python 3.7`. It can be done quickly with the following command: `conda create -n mol_search python=3.7`. Once created, activate the enviroment with `conda activate mol_search`. Once the enviroment is activated you can run the notebook, in our case we are using jupyter, so this can be done running the command `jupyter lab` in this notebooks directory. \n",
    "\n",
    "\n",
    "| Packages |  Servers |\n",
    "| --------------- | -------------- |\n",
    "| pymilvus-orm        | Milvus-2.0.0   |\n",
    "| pymysql           | MySQL          |\n",
    "| numpy |\n",
    "| rdkit |\n",
    "We have included a requirements.txt file in order to easily satisfy the required packages.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f68d163-29f0-454d-b0ac-fad666a2e809",
   "metadata": {},
   "source": [
    "## Up and Running\n",
    "### Installing RDkit\n",
    "Install RDKit with Conda."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ac36625f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting package metadata (current_repodata.json): done\n",
      "Solving environment: done\n",
      "\n",
      "\n",
      "==> WARNING: A newer version of conda exists. <==\n",
      "  current version: 4.9.2\n",
      "  latest version: 4.10.1\n",
      "\n",
      "Please update conda by running\n",
      "\n",
      "    $ conda update -n base -c defaults conda\n",
      "\n",
      "\n",
      "\n",
      "## Package Plan ##\n",
      "\n",
      "  environment location: /Users/filiphaltmayer/opt/miniconda3/envs/test\n",
      "\n",
      "  added / updated specs:\n",
      "    - rdkit\n",
      "\n",
      "\n",
      "The following packages will be downloaded:\n",
      "\n",
      "    package                    |            build\n",
      "    ---------------------------|-----------------\n",
      "    blas-1.0                   |              mkl           6 KB\n",
      "    bzip2-1.0.8                |       h1de35cc_0          75 KB\n",
      "    cairo-1.16.0               |       h8023c5d_1         911 KB\n",
      "    fontconfig-2.13.1          |       ha9ee91d_0         211 KB\n",
      "    freetype-2.10.4            |       ha233b18_0         564 KB\n",
      "    gettext-0.21.0             |       h7535e17_0         2.6 MB\n",
      "    glib-2.68.1                |       hdf23fa2_0         2.7 MB\n",
      "    icu-58.2                   |       h0a44026_3        10.1 MB\n",
      "    intel-openmp-2021.2.0      |     hecd8cb5_564         942 KB\n",
      "    jpeg-9b                    |       he5867d9_2         201 KB\n",
      "    lcms2-2.12                 |       hf1fd2bf_0         284 KB\n",
      "    libboost-1.73.0            |      hd4c2dcd_11        12.7 MB\n",
      "    libiconv-1.16              |       h1de35cc_0         693 KB\n",
      "    libpng-1.6.37              |       ha441bb4_0         262 KB\n",
      "    libtiff-4.2.0              |       h87d7836_0         462 KB\n",
      "    libwebp-base-1.2.0         |       h9ed2024_0         389 KB\n",
      "    libxml2-2.9.10             |       h7cdb67c_3         1.1 MB\n",
      "    llvm-openmp-10.0.0         |       h28b9765_0         236 KB\n",
      "    lz4-c-1.9.3                |       h23ab428_0         140 KB\n",
      "    mkl-2021.2.0               |     hecd8cb5_269       100.0 MB\n",
      "    mkl-service-2.3.0          |   py37h9ed2024_1          43 KB\n",
      "    mkl_fft-1.3.0              |   py37h4a7008c_2         159 KB\n",
      "    mkl_random-1.2.1           |   py37hb2f4e1b_2         260 KB\n",
      "    numpy-1.20.1               |   py37hd6e1bb9_0          22 KB\n",
      "    numpy-base-1.20.1          |   py37h585ceec_0         4.3 MB\n",
      "    olefile-0.46               |           py37_0          49 KB\n",
      "    pandas-1.2.4               |   py37h23ab428_0         8.1 MB\n",
      "    pcre-8.44                  |       hb1e8313_0         185 KB\n",
      "    pillow-8.2.0               |   py37h5270095_0         590 KB\n",
      "    pixman-0.40.0              |       haf1e3a3_0         340 KB\n",
      "    py-boost-1.73.0            |  py37hb2f4e1b_11         189 KB\n",
      "    python-dateutil-2.8.1      |     pyhd3eb1b0_0         221 KB\n",
      "    pytz-2021.1                |     pyhd3eb1b0_0         181 KB\n",
      "    rdkit-2020.09.1.0          |   py37h81f2deb_1        24.1 MB  rdkit\n",
      "    six-1.15.0                 |   py37hecd8cb5_0          27 KB\n",
      "    zstd-1.4.5                 |       h41d2c2f_0         442 KB\n",
      "    ------------------------------------------------------------\n",
      "                                           Total:       173.8 MB\n",
      "\n",
      "The following NEW packages will be INSTALLED:\n",
      "\n",
      "  blas               pkgs/main/osx-64::blas-1.0-mkl\n",
      "  bzip2              pkgs/main/osx-64::bzip2-1.0.8-h1de35cc_0\n",
      "  cairo              pkgs/main/osx-64::cairo-1.16.0-h8023c5d_1\n",
      "  fontconfig         pkgs/main/osx-64::fontconfig-2.13.1-ha9ee91d_0\n",
      "  freetype           pkgs/main/osx-64::freetype-2.10.4-ha233b18_0\n",
      "  gettext            pkgs/main/osx-64::gettext-0.21.0-h7535e17_0\n",
      "  glib               pkgs/main/osx-64::glib-2.68.1-hdf23fa2_0\n",
      "  icu                pkgs/main/osx-64::icu-58.2-h0a44026_3\n",
      "  intel-openmp       pkgs/main/osx-64::intel-openmp-2021.2.0-hecd8cb5_564\n",
      "  jpeg               pkgs/main/osx-64::jpeg-9b-he5867d9_2\n",
      "  lcms2              pkgs/main/osx-64::lcms2-2.12-hf1fd2bf_0\n",
      "  libboost           pkgs/main/osx-64::libboost-1.73.0-hd4c2dcd_11\n",
      "  libiconv           pkgs/main/osx-64::libiconv-1.16-h1de35cc_0\n",
      "  libpng             pkgs/main/osx-64::libpng-1.6.37-ha441bb4_0\n",
      "  libtiff            pkgs/main/osx-64::libtiff-4.2.0-h87d7836_0\n",
      "  libwebp-base       pkgs/main/osx-64::libwebp-base-1.2.0-h9ed2024_0\n",
      "  libxml2            pkgs/main/osx-64::libxml2-2.9.10-h7cdb67c_3\n",
      "  llvm-openmp        pkgs/main/osx-64::llvm-openmp-10.0.0-h28b9765_0\n",
      "  lz4-c              pkgs/main/osx-64::lz4-c-1.9.3-h23ab428_0\n",
      "  mkl                pkgs/main/osx-64::mkl-2021.2.0-hecd8cb5_269\n",
      "  mkl-service        pkgs/main/osx-64::mkl-service-2.3.0-py37h9ed2024_1\n",
      "  mkl_fft            pkgs/main/osx-64::mkl_fft-1.3.0-py37h4a7008c_2\n",
      "  mkl_random         pkgs/main/osx-64::mkl_random-1.2.1-py37hb2f4e1b_2\n",
      "  numpy              pkgs/main/osx-64::numpy-1.20.1-py37hd6e1bb9_0\n",
      "  numpy-base         pkgs/main/osx-64::numpy-base-1.20.1-py37h585ceec_0\n",
      "  olefile            pkgs/main/osx-64::olefile-0.46-py37_0\n",
      "  pandas             pkgs/main/osx-64::pandas-1.2.4-py37h23ab428_0\n",
      "  pcre               pkgs/main/osx-64::pcre-8.44-hb1e8313_0\n",
      "  pillow             pkgs/main/osx-64::pillow-8.2.0-py37h5270095_0\n",
      "  pixman             pkgs/main/osx-64::pixman-0.40.0-haf1e3a3_0\n",
      "  py-boost           pkgs/main/osx-64::py-boost-1.73.0-py37hb2f4e1b_11\n",
      "  python-dateutil    pkgs/main/noarch::python-dateutil-2.8.1-pyhd3eb1b0_0\n",
      "  pytz               pkgs/main/noarch::pytz-2021.1-pyhd3eb1b0_0\n",
      "  rdkit              rdkit/osx-64::rdkit-2020.09.1.0-py37h81f2deb_1\n",
      "  six                pkgs/main/osx-64::six-1.15.0-py37hecd8cb5_0\n",
      "  zstd               pkgs/main/osx-64::zstd-1.4.5-h41d2c2f_0\n",
      "\n",
      "\n",
      "\n",
      "Downloading and Extracting Packages\n",
      "gettext-0.21.0       | 2.6 MB    | ##################################### | 100% \n",
      "six-1.15.0           | 27 KB     | ##################################### | 100% \n",
      "libpng-1.6.37        | 262 KB    | ##################################### | 100% \n",
      "fontconfig-2.13.1    | 211 KB    | ##################################### | 100% \n",
      "icu-58.2             | 10.1 MB   | ##################################### | 100% \n",
      "glib-2.68.1          | 2.7 MB    | ##################################### | 100% \n",
      "pixman-0.40.0        | 340 KB    | ##################################### | 100% \n",
      "jpeg-9b              | 201 KB    | ##################################### | 100% \n",
      "pcre-8.44            | 185 KB    | ##################################### | 100% \n",
      "libboost-1.73.0      | 12.7 MB   | ##################################### | 100% \n",
      "llvm-openmp-10.0.0   | 236 KB    | ##################################### | 100% \n",
      "blas-1.0             | 6 KB      | ##################################### | 100% \n",
      "cairo-1.16.0         | 911 KB    | ##################################### | 100% \n",
      "mkl_random-1.2.1     | 260 KB    | ##################################### | 100% \n",
      "lz4-c-1.9.3          | 140 KB    | ##################################### | 100% \n",
      "pillow-8.2.0         | 590 KB    | ##################################### | 100% \n",
      "libwebp-base-1.2.0   | 389 KB    | ##################################### | 100% \n",
      "numpy-1.20.1         | 22 KB     | ##################################### | 100% \n",
      "mkl-service-2.3.0    | 43 KB     | ##################################### | 100% \n",
      "zstd-1.4.5           | 442 KB    | ##################################### | 100% \n",
      "py-boost-1.73.0      | 189 KB    | ##################################### | 100% \n",
      "bzip2-1.0.8          | 75 KB     | ##################################### | 100% \n",
      "libtiff-4.2.0        | 462 KB    | ##################################### | 100% \n",
      "mkl-2021.2.0         | 100.0 MB  | ##################################### | 100% \n",
      "pytz-2021.1          | 181 KB    | ##################################### | 100% \n",
      "intel-openmp-2021.2. | 942 KB    | ##################################### | 100% \n",
      "libxml2-2.9.10       | 1.1 MB    | ##################################### | 100% \n",
      "python-dateutil-2.8. | 221 KB    | ##################################### | 100% \n",
      "numpy-base-1.20.1    | 4.3 MB    | ##################################### | 100% \n",
      "olefile-0.46         | 49 KB     | ##################################### | 100% \n",
      "mkl_fft-1.3.0        | 159 KB    | ##################################### | 100% \n",
      "rdkit-2020.09.1.0    | 24.1 MB   | ##################################### | 100% \n",
      "freetype-2.10.4      | 564 KB    | ##################################### | 100% \n",
      "lcms2-2.12           | 284 KB    | ##################################### | 100% \n",
      "pandas-1.2.4         | 8.1 MB    | ##################################### | 100% \n",
      "libiconv-1.16        | 693 KB    | ##################################### | 100% \n",
      "Preparing transaction: done\n",
      "Verifying transaction: done\n",
      "Executing transaction: done\n"
     ]
    }
   ],
   "source": [
    "!conda install -c rdkit rdkit -y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00afa67a",
   "metadata": {},
   "source": [
    "### Installing Packages\n",
    "Install the required python packages with `requirements-v2.txt`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "113c1e44",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: numpy in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from -r requirements.txt (line 1)) (1.20.1)\n",
      "Collecting pymilvus==1.1.0\n",
      "  Using cached pymilvus-1.1.0-py3-none-any.whl (56 kB)\n",
      "Collecting redis\n",
      "  Using cached redis-3.5.3-py2.py3-none-any.whl (72 kB)\n",
      "Collecting pymysql\n",
      "  Downloading PyMySQL-1.0.2-py3-none-any.whl (43 kB)\n",
      "\u001b[K     |████████████████████████████████| 43 kB 1.2 MB/s eta 0:00:01\n",
      "\u001b[?25hCollecting ipyplot\n",
      "  Downloading ipyplot-1.1.0-py3-none-any.whl (13 kB)\n",
      "Collecting grpcio>=1.22.0\n",
      "  Downloading grpcio-1.37.1-cp37-cp37m-macosx_10_10_x86_64.whl (3.9 MB)\n",
      "\u001b[K     |████████████████████████████████| 3.9 MB 3.3 MB/s eta 0:00:01\n",
      "\u001b[?25hCollecting grpcio-tools>=1.22.0\n",
      "  Downloading grpcio_tools-1.37.1-cp37-cp37m-macosx_10_10_x86_64.whl (2.0 MB)\n",
      "\u001b[K     |████████████████████████████████| 2.0 MB 19.7 MB/s eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied: requests>=2.22.0 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from pymilvus==1.1.0->-r requirements.txt (line 2)) (2.25.1)\n",
      "Collecting ujson>=2.0.0\n",
      "  Downloading ujson-4.0.2-cp37-cp37m-macosx_10_14_x86_64.whl (45 kB)\n",
      "\u001b[K     |████████████████████████████████| 45 kB 7.9 MB/s  eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied: six>=1.5.2 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from grpcio>=1.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (1.16.0)\n",
      "Requirement already satisfied: setuptools in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from grpcio-tools>=1.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (52.0.0.post20210125)\n",
      "Collecting protobuf<4.0dev,>=3.5.0.post1\n",
      "  Downloading protobuf-3.17.0-cp37-cp37m-macosx_10_9_x86_64.whl (959 kB)\n",
      "\u001b[K     |████████████████████████████████| 959 kB 24.1 MB/s eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied: certifi>=2017.4.17 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from requests>=2.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (2020.12.5)\n",
      "Requirement already satisfied: chardet<5,>=3.0.2 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from requests>=2.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (4.0.0)\n",
      "Requirement already satisfied: idna<3,>=2.5 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from requests>=2.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (2.10)\n",
      "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from requests>=2.22.0->pymilvus==1.1.0->-r requirements.txt (line 2)) (1.26.4)\n",
      "Requirement already satisfied: IPython in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from ipyplot->-r requirements.txt (line 5)) (7.23.1)\n",
      "Requirement already satisfied: pillow in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from ipyplot->-r requirements.txt (line 5)) (8.2.0)\n",
      "Collecting shortuuid\n",
      "  Downloading shortuuid-1.0.1-py3-none-any.whl (7.5 kB)\n",
      "Requirement already satisfied: pickleshare in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (0.7.5)\n",
      "Requirement already satisfied: backcall in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (0.2.0)\n",
      "Requirement already satisfied: pexpect>4.3 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (4.8.0)\n",
      "Requirement already satisfied: jedi>=0.16 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (0.18.0)\n",
      "Requirement already satisfied: matplotlib-inline in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (0.1.2)\n",
      "Requirement already satisfied: appnope in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (0.1.2)\n",
      "Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (3.0.18)\n",
      "Requirement already satisfied: pygments in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (2.9.0)\n",
      "Requirement already satisfied: traitlets>=4.2 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (5.0.5)\n",
      "Requirement already satisfied: decorator in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from IPython->ipyplot->-r requirements.txt (line 5)) (5.0.7)\n",
      "Requirement already satisfied: parso<0.9.0,>=0.8.0 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from jedi>=0.16->IPython->ipyplot->-r requirements.txt (line 5)) (0.8.2)\n",
      "Requirement already satisfied: ptyprocess>=0.5 in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from pexpect>4.3->IPython->ipyplot->-r requirements.txt (line 5)) (0.7.0)\n",
      "Requirement already satisfied: wcwidth in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->IPython->ipyplot->-r requirements.txt (line 5)) (0.2.5)\n",
      "Requirement already satisfied: ipython-genutils in /Users/filiphaltmayer/opt/miniconda3/envs/test/lib/python3.7/site-packages (from traitlets>=4.2->IPython->ipyplot->-r requirements.txt (line 5)) (0.2.0)\n",
      "Installing collected packages: protobuf, grpcio, ujson, shortuuid, grpcio-tools, redis, pymysql, pymilvus, ipyplot\n",
      "Successfully installed grpcio-1.37.1 grpcio-tools-1.37.1 ipyplot-1.1.0 protobuf-3.17.0 pymilvus-1.1.0 pymysql-1.0.2 redis-3.5.3 shortuuid-1.0.1 ujson-4.0.2\n"
     ]
    }
   ],
   "source": [
    "! pip install -r requirements.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cd9e213",
   "metadata": {},
   "source": [
    "### Starting Milvus\n",
    "Start Docker container without mapping the files, if you want to map the paths to the local files to the container, you can refer to [Install and Start Milvus](https://milvus.io/docs/v1.1.0/milvus_docker-cpu.md)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "9a11c5e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating network \"milvus\" with the default driver\n",
      "Creating milvus-minio ... \n",
      "Creating milvus-etcd  ... \n",
      "\u001b[2Bting milvus-minio ... \u001b[32mdone\u001b[0m\u001b[2A\u001b[2KCreating milvus-standalone ... \n",
      "\u001b[1Bting milvus-standalone ... \u001b[32mdone\u001b[0m"
     ]
    }
   ],
   "source": [
    "! docker-compose up -d"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd12c33c",
   "metadata": {},
   "source": [
    "### Starting MySQL\n",
    "We are using MySQL as a metadata storage service. Code can easily be modified to use a python dictionary, but that usually does not work in any use case outside of quick examples. We need a metadata storage service in order to be able to be able to map between embeddings and their corresponding molecular formulae. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "a55dbaa3",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f5ab1dd47e4134385aaa6c10bd8a47b121187d8301629dca314b53f065db591c\n"
     ]
    }
   ],
   "source": [
    "! docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c01aab4-c474-48b5-9dd5-a4d82cc8b02d",
   "metadata": {},
   "source": [
    "### Confirm Running Servers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "942fa94b-d9a5-4906-915e-64be270e981f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CONTAINER ID   IMAGE                                         COMMAND                  CREATED          STATUS                             PORTS                                                  NAMES\n",
      "f5ab1dd47e41   mysql:5.7                                     \"docker-entrypoint.s…\"   23 seconds ago   Up 21 seconds                      0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql\n",
      "dbab8c9596ae   milvusdb/milvus:v2.0.0-rc5-20210830-1c88c8b   \"/tini -- milvus run…\"   25 seconds ago   Up 23 seconds                      0.0.0.0:19530->19530/tcp, :::19530->19530/tcp          milvus-standalone\n",
      "312ec1d7cf17   minio/minio:RELEASE.2020-12-03T00-03-10Z      \"/usr/bin/docker-ent…\"   26 seconds ago   Up 24 seconds (health: starting)   9000/tcp                                               milvus-minio\n",
      "516ed75f7e42   quay.io/coreos/etcd:v3.5.0                    \"etcd -advertise-cli…\"   26 seconds ago   Up 24 seconds                      2379-2380/tcp                                          milvus-etcd\n"
     ]
    }
   ],
   "source": [
    "! docker ps"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d799a811",
   "metadata": {},
   "source": [
    "## Code Overview\n",
    "### Connecting to Servers\n",
    "After preparing the conda environment (installing RDKit, etc.) and successfully starting Milvus and MySQL, we will begin by connecting to the servers. In this case the docker containers are running on localhost and the ports are the default ports.\n",
    "\n",
    "> If there was a problem downloading the required packages and no Milvus module can be found, please run the requirments.txt install again in a terminal that has the conda enviroment activated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "5a3f09d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Connecting to servers\n",
    "\n",
    "import pymysql\n",
    "import time\n",
    "\n",
    "import pymilvus.orm.pymilvus_orm as orm\n",
    "from pymilvus import DataType\n",
    "\n",
    "\n",
    "time.sleep(1)\n",
    "\n",
    "milv = orm.connections.connect(\"default\", host = \"localhost\", port= 19530)\n",
    "conn = pymysql.connect(host='localhost', user='root', port=3306, password='123456', db='mysql', local_infile=True)\n",
    "cursor = conn.cursor()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94fef14e",
   "metadata": {},
   "source": [
    "### Building Collection and Setting Indexing\n",
    "\n",
    "We first begin by declaring some variables, such as the dimension of the vector, the name of the collection/table, the path of the SMILES file and the SMILES molecule we will be searching."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "048db401",
   "metadata": {},
   "outputs": [],
   "source": [
    "VECTOR_DIMENSION = 512\n",
    "collection_name = 'mol_search'\n",
    "file_path = './smiles-data/test_1w.smi'\n",
    "target_smiles = 'Cc1ccc(cc1)S(=O)(=O)N'\n",
    "distance_type = 'JACCARD'\n",
    "index_type = 'BIN_IVF_FLAT'\n",
    "nlist = 512\n",
    "nprobe = 16\n",
    "topk = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "672e59b3-718a-48a4-beca-b195da304f07",
   "metadata": {},
   "source": [
    "For this example we are using the Jaccard distance in order to calculate similarity between vectors. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "74b54643",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "collection created: mol_search\n"
     ]
    }
   ],
   "source": [
    "#Create collection\n",
    "def create_collection(name, milv):\n",
    "    \n",
    "    id_field = orm.schema.FieldSchema(name=\"id_field\", dtype=DataType.INT64, description=\"milv_id\")\n",
    "   \n",
    "    vector_field = orm.schema.FieldSchema(name=\"mol_vectors\", dtype=DataType.BINARY_VECTOR, dim=VECTOR_DIMENSION, description=\"vectors\")\n",
    "\n",
    "    schema = orm.schema.CollectionSchema(fields =[id_field, vector_field], primary_field=\"id_field\", auto_id=False, description=\"collection of mols data\")\n",
    "    \n",
    "    collection = orm.collection.Collection(name=collection_name, schema=schema)\n",
    "    print(\"collection created:\", collection_name)\n",
    "\n",
    "if orm.utility.has_collection(collection_name) == True:\n",
    "        c = orm.collection.Collection(collection_name)\n",
    "        c.drop()\n",
    "        print(\"collection dropped:\", collection_name)\n",
    "\n",
    "cursor.execute('DROP TABLE IF EXISTS ' + collection_name)\n",
    "conn.commit()\n",
    "\n",
    "\n",
    "time.sleep(.1)\n",
    "\n",
    "create_collection(collection_name, milv)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88ace775-b19f-43c8-8d54-249ebe75b992",
   "metadata": {},
   "source": [
    "For the index we are using the binary_ivf_flat index. We can set the index before inserting data so that it indexes concurrently."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "9b440f81-87ab-4979-a235-7aa6bfb1d302",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Created index: {'metric_type': 'JACCARD', 'index_type': 'BIN_IVF_FLAT', 'params': {'nlist': 512}}\n"
     ]
    }
   ],
   "source": [
    "def create_index(name, field_name, milv):\n",
    "    index_param = {\n",
    "        \"metric_type\": distance_type,\n",
    "        \"index_type\": index_type,\n",
    "        \"params\": {\"nlist\": nlist}\n",
    "    }\n",
    "    c = orm.collection.Collection(collection_name)\n",
    "    index = orm.index.Index(c, field_name, index_param)\n",
    "    print(\"Created index: {}\".format(index_param))\n",
    "\n",
    "create_index(collection_name, \"mol_vectors\", milv)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c40791a",
   "metadata": {},
   "source": [
    "### Processing and Inserting Data into Milvus\n",
    "We can use RDKit to generate Morgan fingerprints, which defines a radius from a specific atom and calculates the number of chemical structures within the range of the radius to generate a chemical fingerprint. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "793b9697",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Process SMILES\n",
    "\n",
    "from rdkit import Chem\n",
    "from rdkit.Chem import AllChem\n",
    "from rdkit import DataStructs\n",
    "\n",
    "def smiles_to_vec(smiles):\n",
    "    mols = Chem.MolFromSmiles(smiles)\n",
    "    fp = AllChem.GetMorganFingerprintAsBitVect(mols, 2, VECTOR_DIMENSION)\n",
    "    hex_fp = DataStructs.BitVectToFPSText(fp)\n",
    "    vec = bytes.fromhex(hex_fp)\n",
    "    return vec\n",
    "\n",
    "def get_vectors(file_path):\n",
    "    ids = []\n",
    "    vectors = []\n",
    "    with open(file_path, 'r') as f:\n",
    "        for line in f:\n",
    "            line = line.strip().split()\n",
    "            smiles = line[0]\n",
    "            num = int(line[1])\n",
    "            vec = smiles_to_vec(smiles)\n",
    "            vectors.append(vec)\n",
    "            ids.append(num)\n",
    "    return vectors, ids"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcab58f9",
   "metadata": {},
   "source": [
    "Then we can then read the SMILES file and batch insert it into Milvus."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "7e249e8a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "RDKit WARNING: [15:03:27] WARNING: not removing hydrogen atom without neighbors\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(insert count: 10000, delete count: 0, upsert count: 0, timestamp: 427457304429854721)"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Batch insert\n",
    "\n",
    "vectors, ids = get_vectors(file_path)\n",
    "c = orm.collection.Collection(collection_name)\n",
    "c.insert([ids, vectors])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32761975",
   "metadata": {},
   "source": [
    "### Loading Mapping into MySQL\n",
    "After recieving the IDs from milvus we must save the mapping between the vector and the SMILES molecule. \n",
    "We do this buy saving the (ID, SMILES) pair in a table."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "f6f9f0e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the table in MySQL\n",
    "\n",
    "def create_table_mysql(conn, cursor, table_name):\n",
    "    sql = \"create table if not exists \" + table_name + \"(smiles varchar(256), ids bigint);\"\n",
    "    try:\n",
    "        cursor.execute(sql)\n",
    "        conn.commit()\n",
    "    except Exception as e:\n",
    "        print(\"MYSQL ERROR:\", e, sql)\n",
    "\n",
    "# Load the local file to MySQL\n",
    "\n",
    "def load_data_mysql(conn, cursor, table_name, file_path):\n",
    "    sql = \"load data local infile '\" + file_path + \"' into table \" + table_name + \";\"\n",
    "    try:\n",
    "        cursor.execute(sql)\n",
    "        conn.commit()\n",
    "    except Exception as e:\n",
    "        print(\"MYSQL ERROR:\", e, sql)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "28f75491",
   "metadata": {},
   "outputs": [],
   "source": [
    "create_table_mysql(conn, cursor, collection_name)\n",
    "load_data_mysql(conn, cursor, collection_name, file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39e780ab",
   "metadata": {},
   "source": [
    "### Searching\n",
    "With the data loaded in Milvus and the mapping loaded into MySQL, we can now perform the search. To begin, we must first transform the SMILES molecule into a molecular fingerprint. Then, with this fingerprint we can search the database, which gives us the closest IDs with their distances.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "10bc65fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Perform search\n",
    "def search(name, vector_field, smiles):\n",
    "    search_vectors = [smiles_to_vec(smiles)]\n",
    "    nq = len(search_vectors)\n",
    "    search_params = {\"metric_type\": distance_type, \"params\": {\"nprobe\": nprobe}}\n",
    "    \n",
    "    c = orm.collection.Collection(collection_name)\n",
    "    \n",
    "    results = c.search(search_vectors, vector_field, search_params, topk)\n",
    "    \n",
    "    re_ids = [x.id for x in results[0]]\n",
    "    re_distance = [x.distance for x in results[0]]\n",
    "    return re_ids, re_distance"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b45dc633-fb5f-44c1-8178-6ba5a83f4976",
   "metadata": {},
   "source": [
    "With the IDs we can then search the MySQL table to find the corresponding SMILES molecules."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "1a5d4c61",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get SMILES results\n",
    "def search_by_milvus_id(conn, cursor, collection_name, ids):\n",
    "    str_ids = str(ids).replace('[','').replace(']','')\n",
    "    sql = \"select smiles from \" + collection_name + \" where ids in (\" + str_ids + \") order by field (ids, \" + str_ids + \");\"\n",
    "    try:\n",
    "        cursor.execute(sql)\n",
    "        results = cursor.fetchall()\n",
    "        results = [res[0] for res in results]\n",
    "        return results\n",
    "    except Exception as e:\n",
    "        print(\"MYSQL ERROR:\", e, sql)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "4809c648-b3b0-43de-bd98-06a50bc9c31a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reload collection: for now safer to release and load before search\n",
    "def reload_collection():\n",
    "    c = orm.collection.Collection(collection_name)\n",
    "    c.release()\n",
    "    c.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "fcdd9056",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Cc1ccc(cc1)S(=O)(=O)N', 'Cc1ccc(cc1)S(=O)(=O)Cl', 'Nc1ccc(cc1)S(=O)(=O)N']\n"
     ]
    }
   ],
   "source": [
    "reload_collection()\n",
    "\n",
    "re_ids, re_distance = search(collection_name, \"mol_vectors\", target_smiles)\n",
    "re_smiles = search_by_milvus_id(conn, cursor, collection_name, re_ids)\n",
    "print(re_smiles)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "f6d642b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "target-smiles: Cc1ccc(cc1)S(=O)(=O)N\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "results: \n",
      "Cc1ccc(cc1)S(=O)(=O)N \tids: 6269 \tdistance: 0.0\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cc1ccc(cc1)S(=O)(=O)Cl \tids: 7397 \tdistance: 0.3636363744735718\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Nc1ccc(cc1)S(=O)(=O)N \tids: 5333 \tdistance: 0.40909090638160706\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#Display Results\n",
    "\n",
    "from rdkit.Chem import Draw\n",
    "\n",
    "def draw_smiles(smiles):\n",
    "    mols = Chem.MolFromSmiles(smiles)\n",
    "    img = Draw.MolsToGridImage([mols], molsPerRow=1, subImgSize=(150, 150))\n",
    "    return img\n",
    "\n",
    "img = draw_smiles(target_smiles)\n",
    "print(\"target-smiles:\", target_smiles)\n",
    "display(img)\n",
    "print(\"results: \")\n",
    "\n",
    "for s, i, d in zip(re_smiles,re_ids, re_distance):\n",
    "    print(s, \"\\tids:\", i, \"\\tdistance:\", d)\n",
    "    display(draw_smiles(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40563145",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "This notebook shows how to search for similar molecules. We can also search the superstructure and substructure with the different [METRICS](https://milvus.io/docs/v1.1.0/metric.md#floating) when searching in Milvus.\n",
    "\n",
    "We have also built a [demo system](https://zilliz.com/milvus-demos), and also have an open source projects on substructure and superstructure search in [MolSearch](https://github.com/zilliztech/MolSearch). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "a09796bc-4044-4a30-8bbd-02768be847c8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Stopping milvus-standalone ... \n",
      "Stopping milvus-minio      ... \n",
      "Stopping milvus-etcd       ... \n",
      "\u001b[2Bping milvus-minio      ... \u001b[32mdone\u001b[0m\u001b[1A\u001b[2K\u001b[2A\u001b[2KRemoving milvus-standalone ... \n",
      "Removing milvus-minio      ... \n",
      "Removing milvus-etcd       ... \n",
      "\u001b[3BRemoving network milvus... \u001b[32mdone\u001b[0m\u001b[2A\u001b[2K\n"
     ]
    }
   ],
   "source": [
    "! docker-compose down"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "5dc96870-d3a9-4b34-933b-9f5ac6f05e04",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mysql\n",
      "mysql\n"
     ]
    }
   ],
   "source": [
    "!docker stop mysql\n",
    "!docker rm  mysql\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "601c20a2-c05d-4001-9a4d-4a6ffa5a21b0",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
